fix: persist CREDENTIAL_SECRET in DATA_DIR to survive npm-global upgrades - #97
Closed
siglimumuni wants to merge 1 commit into
Closed
fix: persist CREDENTIAL_SECRET in DATA_DIR to survive npm-global upgrades#97siglimumuni wants to merge 1 commit into
siglimumuni wants to merge 1 commit into
Conversation
…ades CREDENTIAL_SECRET was persisted to .env.local inside process.cwd(), which for npm-global installs is the per-version build directory (~/.swarmclaw/builds/package-<ver>/.next/standalone/). Each upgrade lands in a fresh build dir with a fresh .env.local, the auto-generate branch fires (since the env var is unset in the new cwd), and a brand-new secret gets written. Net effect: every npm-global upgrade silently regenerates CREDENTIAL_SECRET. All previously-encrypted credentials — Slack bot tokens, provider API keys, agent credential injections — silently fail to decrypt (the failure goes into try/catch and produces empty values). Slack connectors come up with "No bot token configured." Agent execute shells get empty env vars. Recovery requires either restoring the old secret manually or re-entering every credential. Fix: store the secret in a dedicated file in DATA_DIR (`<DATA_DIR>/credential-secret`), which is stable across upgrades and volume-mounted in Docker. Resolution precedence is now: 1. process.env.CREDENTIAL_SECRET (already-set, e.g. by orchestrator) 2. <DATA_DIR>/credential-secret ← the new stable home 3. .env files (legacy — values from loadEnv()) 4. Generate + persist to <DATA_DIR>/credential-secret Migration path is automatic: on first launch after upgrade, an existing secret found via .env.local gets copied into the dedicated file. Future upgrades read from the file directly. If both exist and disagree, the file wins (with a log.warn) — that case usually means a stale .env.local was generated by a previous regression and would orphan working credentials if used. File permissions: 0o600 on the secret file. Concrete repro: install v1.9.31, configure agents with credentials, `npm install -g @swarmclawai/swarmclaw@1.9.32`, restart service. Every credential becomes unreadable. Files: - src/lib/server/storage-auth.ts
Member
|
Cherry-picked and shipped in v1.9.33 with additional precedence and migration tests for DATA_DIR credential-secret handling. Thanks for the fix. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
`CREDENTIAL_SECRET` is persisted via `appendEnvKeyIfMissing` to `.env.local` inside `process.cwd()`. For npm-global installs, `cwd` is the per-version build directory (`~/.swarmclaw/builds/package-/.next/standalone/`). Each upgrade lands in a fresh build dir with a fresh empty `.env.local`; the auto-generate branch in `storage-auth.ts` fires (since the env var is unset in the new cwd); a brand-new 32-byte secret gets written.
Net effect: every npm-global upgrade silently regenerates `CREDENTIAL_SECRET`. Every credential encrypted under the prior secret becomes unreadable. The decrypt failure goes into `try/catch` blocks throughout the codebase and produces empty values — Slack connectors come up with `"No bot token configured"`, agent execute shells get empty env vars, every provider key in Settings → Providers needs to be re-entered.
How I hit this
Upgraded a running install from v1.9.31 → v1.9.32 (`npm install -g @swarmclawai/swarmclaw@1.9.32`). After `launchctl kickstart` brought the service back:
`diff`'d the two `.env.local`s:
```
v1.9.31: CREDENTIAL_SECRET=ac7e4c83…
v1.9.32: CREDENTIAL_SECRET=5529ec0f… ← regenerated during the upgrade
```
Restoring the old secret into v1.9.32's `.env.local` and restarting unblocked everything. But that workaround breaks on every future upgrade.
Fix
Store the secret in a dedicated file in `DATA_DIR` (`<DATA_DIR>/credential-secret`), which is stable across npm-global upgrades and already volume-mounted in Docker. Resolution precedence in `storage-auth.ts`:
Migration is automatic: on first launch after this fix lands, an existing secret found via `.env.local` gets copied into the dedicated file. Future upgrades read from the file directly. File permissions: `0o600`.
If both the env-loaded value AND the file are set and disagree, the file wins (with a `log.warn`) — that case typically means a stale `.env.local` was regenerated during install and would orphan working credentials if trusted.
Files
Test plan
Notes
`ACCESS_KEY` has the same per-version-cwd persistence problem (and gets regenerated on every upgrade too) but isn't as catastrophic — it just forces an auth re-login. Left for a follow-up if there's interest in symmetric treatment.
🤖 Generated with Claude Code